﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace PickGold.Util.WordSpliter
{
	/// <summary>
	/// Dictionary for word
	/// </summary>
	class WordDictionary
	{
		internal Dictionary<string, Keyword> Keywords;
		internal Dictionary<char, Keyword> FirstChars;
		internal Dictionary<uint, Keyword> DoubleChars;
		internal Dictionary<long, byte[]> TripleChars;
		internal ChineseName ChineseName;
		internal List<Wildcard> Wildcards; //通配符词汇列表

		public WordDictionary(Keyword[] keywords, ChineseName chineseName)
		{
			this.Keywords = new Dictionary<string, Keyword>();
			this.FirstChars = new Dictionary<char, Keyword>();
			this.DoubleChars = new Dictionary<uint, Keyword>();
			this.TripleChars = new Dictionary<long, byte[]>();
			this.ChineseName = chineseName;
			foreach (var kw in keywords)
			{
				var key = kw.Word.ToLower();

				if (key.Length == 1)
				{
					FirstChars[key[0]] = kw;
					continue;
				}

				if (key.Length == 2)
				{
					DoubleChars[((uint)key[0] * 65536) + key[1]] = kw;
					continue;
				}

				if (Keywords.ContainsKey(key))
					continue;

				this.Keywords[key] = kw;
				var tripleChar = ((long)key[0]) * 0x100000000 + (uint)(key[1] * 65536) + key[2];
				var wordLenArray = Reflector<byte>.NullArray;
				if (!TripleChars.TryGetValue(tripleChar, out wordLenArray))
				{
					wordLenArray = new byte[4];
					wordLenArray[0] = (byte)key.Length;
					TripleChars[tripleChar] = wordLenArray;
					continue;
				}

				var i = 0;
				var find = false;
				for (; i < wordLenArray.Length; i++)
				{
					var len = wordLenArray[i];
					if (len == key.Length)
					{
						find = true;
						break;
					}

					if (len == 0)
					{
						wordLenArray[i] = (byte)key.Length;
						find = true;
						break;
					}
				}

				if (find)
					continue;

				var temp = new byte[wordLenArray.Length * 2];
				wordLenArray.CopyTo(temp, 0);
				wordLenArray = temp;
				wordLenArray[i] = (byte)key.Length;
				TripleChars[tripleChar] = wordLenArray;
			}
		}

		public int Count
		{
			get
			{
				if (Keywords == null)
					return 0;

				return Keywords.Count + FirstChars.Count + DoubleChars.Count;
			}
		}

		public Keyword GetKeyword(string word)
		{
			var kw = Reflector<Keyword>.DefaultValue;
			if (word.Length == 1)
			{
				FirstChars.TryGetValue(word.ToLower()[0], out kw);
				return kw;
			}

			if (word.Length == 2)
			{
				word = word.ToLower();
				var doubleChar = ((uint)word[0] * 65536) + word[1];
				DoubleChars.TryGetValue(doubleChar, out kw);
				return kw;
			}

			Keywords.TryGetValue(word.ToLower(), out kw);
			return kw;
		}

		public AppendList<SearchMatch> GetAllMatchs(string text, bool chineseNameIdentify)
		{
			var result = new AppendList<SearchMatch>();
			if (string.IsNullOrWhiteSpace(text))
				return result;

			var keyText = text;
			if (text[0] < 128)
				keyText = keyText.ToLower();
			for (var i = 0; i < text.Length; i++)
			{

				var fst = keyText[i];
				var lenList = Reflector<byte>.NullArray;
				var chsNames = Reflector<string>.NullListI;
				if (chineseNameIdentify)
				{
					chsNames = ChineseName.Match(text, i);
					if (chsNames != null)
					{
						foreach (var name in chsNames)
						{
							var kw = new Keyword(name, KeywordFlags.Name, 0);
							result.Add(new SearchMatch(i, name.Length, kw));
						}
					}
				}


				var fwa = Reflector<Keyword>.DefaultValue;
				if (FirstChars.TryGetValue(fst, out fwa))
					result.Add(new SearchMatch(i, 1, fwa));

				if (i < keyText.Length - 1)
				{
					var doubleChar = ((uint)keyText[i] * 65536) + keyText[i + 1];
					if (DoubleChars.TryGetValue(doubleChar, out fwa))
						result.Add(new SearchMatch(i, 2, fwa));
				}

				if (i >= keyText.Length - 2)
					continue;

				var tripleChar = ((long)keyText[i]) * 0x100000000 + (uint)(keyText[i + 1] * 65536) + keyText[i + 2];

				if (TripleChars.TryGetValue(tripleChar, out lenList))
				{
					foreach (var len in lenList)
					{
						if (len == 0)
							break;

						if (i + len > keyText.Length)
							continue;

						var key = keyText.Substring(i, len);
						var wa = Reflector<Keyword>.DefaultValue;
						if (Keywords.TryGetValue(key, out wa))
						{
							if (chsNames != null)
							{
								var find = false;
								foreach (var name in chsNames)
								{
									if (wa.Word == name)
									{
										find = true;
										break;
									}
								}

								if (find)
									continue;
							}

							result.Add(new SearchMatch(i, len, wa));
						}
					}
				}
			}

			return result;
		}

		internal List<Wildcard> GetWildcards(string word)
		{
			if (string.IsNullOrEmpty(word))
				return null;

			var result = new List<Wildcard>();
			if (this.Wildcards == null || this.Wildcards.Count == 0)
				return result;

			word = word.ToLower().Trim();
			foreach (var wi in Wildcards)
			{
				if (wi.Key.Contains(word))
					result.Add(wi);
			}
			return result;
		}

		public void InsertWord(String word, double frequency, KeywordFlags flags)
		{
			if (Keywords == null)
				return;

			var key = word.ToLower();

			if (key.Length == 1 && FirstChars.ContainsKey(key[0]))
			{
				FirstChars[key[0]].Flags = flags;
				FirstChars[key[0]].Frequency += frequency;
				return;
			}

			if (key.Length == 2)
			{
				var doubleChar = ((uint)key[0] * 65536) + key[1];
				if (DoubleChars.ContainsKey(doubleChar))
				{
					DoubleChars[doubleChar].Flags = flags;
					DoubleChars[doubleChar].Frequency += frequency;

					return;
				}
			}

			if (Keywords.ContainsKey(key))
			{
				Keywords[key].Flags = flags;
				Keywords[key].Frequency += frequency;
				return;
			}

			var wa = new Keyword(word, flags, frequency);

			if (key.Length == 1 && !FirstChars.ContainsKey(key[0]))
			{
				FirstChars.Add(key[0], wa);
				return;
			}

			if (key.Length == 2)
			{
				var doubleChar = ((uint)key[0] * 65536) + key[1];
				if (!DoubleChars.ContainsKey(doubleChar))
				{
					DoubleChars.Add(doubleChar, wa);
					return;
				}
			}

			Keywords.Add(key, wa);

			var tripleChar = ((long)key[0]) * 0x100000000 + (uint)(key[1] * 65536) + key[2];

			byte[] wordLenArray;
			if (!TripleChars.TryGetValue(tripleChar, out wordLenArray))
			{
				wordLenArray = new byte[4];
				wordLenArray[0] = (byte)key.Length;
				TripleChars.Add(tripleChar, wordLenArray);
				return;
			}

			var i = 0;
			var find = false;
			for (; i < wordLenArray.Length; i++)
			{
				var len = wordLenArray[i];
				if (len == key.Length)
				{
					find = true;
					break;
				}

				if (len == 0)
				{
					wordLenArray[i] = (byte)key.Length;
					find = true;
					break;
				}
			}

			if (!find)
			{
				var temp = new byte[wordLenArray.Length * 2];
				wordLenArray.CopyTo(temp, 0);
				wordLenArray = temp;
				wordLenArray[i] = (byte)key.Length;

				TripleChars[tripleChar] = wordLenArray;
			}
		}

		public void UpdateWord(string word, double frequency, KeywordFlags flags)
		{
			var key = word.ToLower();

			if (key.Length == 1)
			{
				if (FirstChars.ContainsKey(key[0]))
				{
					FirstChars[key[0]].Word = word;
					FirstChars[key[0]].Frequency = frequency;
					FirstChars[key[0]].Flags = flags;
				}
				return;
			}

			if (key.Length == 2)
			{
				var doubleChar = ((uint)key[0] * 65536) + key[1];
				if (DoubleChars.ContainsKey(doubleChar))
				{
					DoubleChars[doubleChar].Word = word;
					DoubleChars[doubleChar].Frequency = frequency;
					DoubleChars[doubleChar].Flags = flags;
				}
				return;
			}

			if (Keywords == null)
				return;

			if (!Keywords.ContainsKey(key))
				return;

			Keywords[key].Word = word;
			Keywords[key].Frequency = frequency;
			Keywords[key].Flags = flags;
		}

		public void DeleteWord(string word)
		{
			var key = word.ToLower();

			if (key.Length == 1)
			{
				if (FirstChars.ContainsKey(key[0]))
					FirstChars.Remove(key[0]);
				return;
			}

			if (key.Length == 2)
			{
				uint doubleChar = ((uint)key[0] * 65536) + key[1];
				if (DoubleChars.ContainsKey(doubleChar))
					DoubleChars.Remove(doubleChar);
				return;
			}

			if (Keywords == null)
				return;

			if (Keywords.ContainsKey(key))
				Keywords.Remove(key);
		}

		/// <summary>
		/// 通过遍历方式搜索
		/// </summary>
		/// <returns></returns>
		public List<SearchResult> Search(String key)
		{
			Debug.Assert(Keywords != null);

			var result = new List<SearchResult>();

			foreach (var kw in FirstChars.Values)
			{
				if (kw.Word.Contains(key))
				{
					var sr = new SearchResult();
					sr.Word = kw;
					sr.SimilarRatio = (float)key.Length / (float)kw.Word.Length;
					result.Add(sr);
				}
			}

			foreach (var kw in DoubleChars.Values)
			{
				if (kw.Word.Contains(key))
				{
					var sr = new SearchResult();
					sr.Word = kw;
					sr.SimilarRatio = (float)key.Length / (float)kw.Word.Length;
					result.Add(sr);
				}
			}

			foreach (var kw in Keywords.Values)
			{
				if (kw.Word.Contains(key))
				{
					var sr = new SearchResult();
					sr.Word = kw;
					sr.SimilarRatio = (float)key.Length / (float)kw.Word.Length;
					result.Add(sr);
				}
			}

			return result;
		}

		public List<SearchResult> SearchByLength(int len)
		{
			Debug.Assert(Keywords != null);

			var result = new List<SearchResult>();

			foreach (var kw in Keywords.Values)
			{
				if (kw.Word.Length == len)
				{
					var sr = new SearchResult();
					sr.Word = kw;
					sr.SimilarRatio = 0;
					result.Add(sr);
				}
			}

			return result;
		}

		public List<SearchResult> SearchByFlags(KeywordFlags flags)
		{
			Debug.Assert(Keywords != null);

			var result = new List<SearchResult>();

			foreach (var kw in Keywords.Values)
			{
				if ((kw.Flags & flags) != 0)
				{
					var sr = new SearchResult();
					sr.Word = kw;
					sr.SimilarRatio = 0;
					result.Add(sr);
				}
			}

			return result;
		}
	}

	public class SearchResult : IComparable
	{
		/// <summary>
		/// 单词
		/// </summary>
		public Keyword Word;

		/// <summary>
		/// 相似度
		/// </summary>
		public float SimilarRatio;

		public override string ToString()
		{
			return Word.Word;
		}

		#region IComparable Members

		public int CompareTo(object obj)
		{
			SearchResult dest = (SearchResult)obj;

			if (this.SimilarRatio == dest.SimilarRatio)
			{
				return 0;
			}
			else if (this.SimilarRatio > dest.SimilarRatio)
			{
				return -1;
			}
			else
			{
				return 1;
			}
		}

		#endregion
	}

	public struct SearchMatch
	{
		public int Level;
		public int Position;
		public int Length;
		public Keyword Word;

		public SearchMatch(int position, int length, Keyword word)
		{
			this.Position = position;
			this.Length = length;
			this.Word = word;
			this.Level = 0;
		}

		public override string ToString()
		{
			return string.Format("{0},{1}", Word.Word, Position);
		}
	}

}
